using System;
using System.Collections;
using System.Text;

namespace HtmlParser.Nodes
{
    public class Element : Node
    {
        private Element _owner = null;
        private Element _endElement = null;
        private HtmlParser.AttributeCollection _attributes;
        private string _elementName = "";
        private bool _isEndElement = false;
        private bool _isStandAlone = false;
        private NodeCollection _childNodes = new NodeCollection();
		
		public Element Owner
		{
			get { return _owner; }
			set { _owner = value; }
		}

		public HtmlParser.AttributeCollection Attributes
		{
			get { return _attributes; }
		}

		public string TagName
		{
			get { return _elementName; }
			set 
			{
				if((_endElement != null) && (!_isEndElement)) _endElement.TagName = value;
				_elementName = value; 
			}
		}

		public bool IsEndElement
		{
			get { return _isEndElement; }
		}

		public bool IsStandAlone
		{
			get { return _isStandAlone; }
			set
			{
				_isStandAlone = value;
				_endElement = null;
			}
		}

		public Element EndElement
		{
			get { return _endElement; }
			set { _endElement = value; }
		}

		public NodeCollection ChildNodes
		{
			get { return _childNodes; }
		}

		public System.Collections.Generic.List<Nodes.Element> ChildElements
		{
			get 
			{
				System.Collections.Generic.List<Nodes.Element> result = new System.Collections.Generic.List<Element>();
				foreach (Node node in _childNodes) if (node is Element) result.Add(node as Element);
				return result;
				//***LINQ VERSION*** return (from node in _childNodes where node is Nodes.Element select node as Nodes.Element).ToList();
			}
		}

		public Element(string html)
		{
			this.SetHtml(html);
		}

        public override string GetHtml()
        {
            return OuterHTML;
        }

		public override void SetHtml(string html)
		{
			html = html.Replace('\n', ' ');
			html = html.Replace('\r', ' ');
			html = html.Replace('\f', ' ');

			string tagName = "";
			string attributes = "";

			bool isEndTag = false;
			bool isStandAlone = false;

			int endLength = 1;
			int beginLength = 1;

			if (html.StartsWith("</"))
			{
				isEndTag = true;
				beginLength = 2;
			}

			if (html.EndsWith("/>"))
			{
				isStandAlone = true;
				endLength = 2;
			}

			int firstWhiteSpace = html.IndexOf(" ");

			if (firstWhiteSpace == -1)
			{
				tagName = html.Substring(beginLength, html.Length - endLength - beginLength);
				attributes = "";
			}
			else
			{
				tagName = html.Substring(beginLength, firstWhiteSpace - beginLength);
				attributes = html.Substring(firstWhiteSpace, html.Length - firstWhiteSpace - endLength);
			}		

			ParseAttributes(attributes);

			_elementName = tagName;
			_isEndElement = isEndTag;
			_isStandAlone = isStandAlone;
		}


        public override string GetSubType()
        {
            return _elementName;
        }

        public void AppendChild(Nodes.Node element)
        {
            if (element.Parent != null)
            {
				element.Parent.RemoveChild(element);
            }
            _childNodes.Add(element);
			element.Parent = this;
        }

        public void RemoveChild(Nodes.Node element)
        {
            _childNodes.Remove(element);
			element.Parent = null;
        }

        public string InnerHTML
        {
            get
            {
                StringBuilder result = new StringBuilder();
                foreach (Nodes.Node element in this.ChildNodes)
                {
					result.Append(element.GetHtml());
                }

                return result.ToString();

            }
            set
            {
                RemoveAllChildElements();
                //* TODO 
                //Document.Parse(value,this);
            }
        }

        public string OuterHTML
        {
            get
            {
                StringBuilder result = new StringBuilder();
                result.Append(this.TagHTML);
                if (this.EndElement != null)
                {
                    result.Append(this.InnerHTML);
                    result.Append(this.EndElement.TagHTML);
                }
                return result.ToString();
            }
        }

        public void RemoveAllChildElements()
        {
			//* TODO
        }

        public string TagHTML
        {
            get
            {
                StringBuilder result = new StringBuilder();
                result.Append("<");
                if (this.IsEndElement)
                {
                    result.Append("/");
                }

                result.Append(this.TagName);

                foreach (HtmlParser.Attribute attrib in this.Attributes)
                {
                    result.Append(" " + attrib.Name + "=\"" + attrib.Value + "\"");
                }

                //if (((this.IsStandAlone) || (this.EndElement == null)) && (!this.IsEndTag))
				if ((this.IsStandAlone) && (!this.IsEndElement))
                {
                    result.Append(" /");
                }
                result.Append(">");
                return result.ToString();
            }
        }


		public void ParseAttributes(string attributes)
		{
			_attributes = new AttributeCollection();

			bool readingAttrib = false;
			bool readingAttribName = false;
			bool readingAttribValue = false;
			bool quotedValue = false;
			char quoteChar = '"';

			string attributeName = "";

			char[] buffer = new char[attributes.Length];
			int bufferIndex = 0;

			foreach(char chr in attributes)
			{
				if((!readingAttrib) && (chr != ' '))
				{
					//* Reading any attribute but an none whitespace character was found
					readingAttrib = true;
					readingAttribName = true;
					readingAttribValue = false;
					quotedValue = false;

					buffer[bufferIndex] = chr;
					bufferIndex++;
				}
				else if(readingAttrib)
				{
					if ((readingAttribName) && (chr == ' '))
					{
						//* Reading the attribute name but now an whitespace character has been found, so the attribute's name is found
						readingAttribName = false;
					}
					else if ((readingAttribName) && (chr == '='))
					{
						//* Reading the attribute name but now an "=" character has been found, so the attribute's name is found and value is starting
						readingAttribName = false;
						readingAttribValue = true;

						attributeName = new string(buffer,0,bufferIndex);

						Utils.CleanUpCA(buffer);
						bufferIndex = 0;
					}
					else if (readingAttribName)
					{
						//* Reading the attribute name and only an whitespace or = can end that. (tag end is handles elsewhere)
						buffer[bufferIndex] = chr;
						bufferIndex++;
					}
					else if ((!readingAttribName) && (!readingAttribValue) && (chr == ' '))
					{ 
						//* Reading attriubte but not the name or value this must be some white space before the = or next attribute
					}
					else if ((!readingAttribName) && (!readingAttribValue) && (chr != '='))
					{
						//* Reading attrib but not the name or value and the character is not =
						//* flag attribute
						readingAttrib = false;
						HtmlParser.Attribute attrib = new HtmlParser.Attribute(new string(buffer,0,bufferIndex),null);
						_attributes.Add(attrib);
					}
					else if ((!readingAttribName) && (!readingAttribValue) && (chr == '='))
					{
						//* Start reading value
						readingAttribValue = true;							
						readingAttribName = false;

						attributeName = new string(buffer,0,bufferIndex);

						Utils.CleanUpCA(buffer);
						bufferIndex = 0;						
					}
					else if (readingAttribValue)
					{
						if (((chr == '"') || (chr == '\'')) && (!quotedValue) && (bufferIndex == 0))
						{
							//* first quote eather single or double quoute
							quotedValue = true;
							quoteChar = chr;
						}
						else if ((chr == quoteChar) && (quotedValue))
						{
							//* Quoted value ends
							readingAttribValue = false;
							readingAttribName = false;
							readingAttrib = false;

							HtmlParser.Attribute attrib = new HtmlParser.Attribute(attributeName,new string(buffer,0,bufferIndex));
							_attributes.Add(attrib);

							Utils.CleanUpCA(buffer);							
							bufferIndex = 0;

							attributeName = "";
						}
						else if ((bufferIndex == 0) && (chr == ' '))
						{
							//* keep on reading
						}
						else if ((!quotedValue) && (bufferIndex != 0) && (chr == ' '))
						{
							//* found unqouted value
							readingAttribValue = false;
							readingAttribName = false;
							readingAttrib = false;

							HtmlParser.Attribute attrib = new HtmlParser.Attribute(attributeName,new string(buffer,0,bufferIndex));
							_attributes.Add(attrib);

							Utils.CleanUpCA(buffer);
							bufferIndex = 0;

							attributeName = "";
						}
						else
						{
							buffer[bufferIndex] = chr;
							bufferIndex++;
						}
					}
				}
			}

			//* Finish
			if (readingAttrib)
			{
				if (readingAttribName)
				{
					//* Flag attrib
					HtmlParser.Attribute attrib = new HtmlParser.Attribute(new string(buffer,0,bufferIndex),null);
					_attributes.Add(attrib);
				}
				if (readingAttribValue)
				{
					//* Unqouted attrib
					HtmlParser.Attribute attrib = new HtmlParser.Attribute(attributeName,new string(buffer,0,bufferIndex));
					_attributes.Add(attrib);
				}
			}			
		}

    }
}
